
(function() {

    function convert( match, nosign ) {
        if (nosign) {
            match.sign = '';
        } else {
            match.sign = match.negative ? '-' : match.sign;
        }
        var l = match.min - match.argument.length + 1 - match.sign.length;
        var pad = new Array(l < 0 ? 0 : l).join(match.pad);
        if (!match.left) {
            if (match.pad == "0" || nosign) {
                return match.sign + pad + match.argument;
            } else {
                return pad + match.sign + match.argument;
            }
        } else {
            if (match.pad == "0" || nosign) {
                return match.sign + match.argument + pad.replace(/0/g, ' ');
            } else {
                return match.sign + match.argument + pad;
            }
        }
    }

    this.lucidapi = {

        escapeEntities: function(value)
        {
            function replaceChars(ch)
            {
                switch (ch)
                {
                    case "<":
                        return "&lt;";
                    case ">":
                        return "&gt;";
                    case "&":
                        return "&amp;";
                    case "'":
                        return "&#39;";
                    case '"':
                        return "&quot;";
                }
                return "?";
            };
            return String(value).replace(/[<>&"']/g, replaceChars);
        },

        absorb: function(destination, source) {
          for (var property in source) {
            destination[property] = source[property];
          }
          return destination;
        },


        sprintf : function () {
            /*

            Possible format values:

                %% - Returns a percent sign
                %b - Binary number
                %c - The character according to the ASCII value
                %d - Signed decimal number
                %f - Floating-point number
                %o - Octal number
                %s - String
                %x - Hexadecimal number (lowercase letters)
                %X - Hexadecimal number (uppercase letters)

            Additional format values. These are placed between the % and the letter (example %.2f):

                + (Forces both + and - in front of numbers. By default, only negative numbers are marked)
                - (Left-justifies the variable value)
                0 zero will be used for padding the results to the right string size
                [0-9] (Specifies the minimum width held of to the variable value)
                .[0-9] (Specifies the number of decimal digits or maximum string length)

            */

            if (typeof arguments == "undefined") { return null; }
            if (arguments.length < 1) { return null; }
            if (typeof arguments[0] != "string") { return null; }
            if (typeof RegExp == "undefined") { return null; }

            var string = arguments[0];
            var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
            var matches = new Array();
            var strings = new Array();
            var convCount = 0;
            var stringPosStart = 0;
            var stringPosEnd = 0;
            var matchPosEnd = 0;
            var newString = '';
            var match = null;

            while (match = exp.exec(string)) {
                if (match[9]) { convCount += 1; }

                stringPosStart = matchPosEnd;
                stringPosEnd = exp.lastIndex - match[0].length;
                strings[strings.length] = string.substring(stringPosStart, stringPosEnd);

                matchPosEnd = exp.lastIndex;
                matches[matches.length] = {
                    match: match[0],
                    left: match[3] ? true : false,
                    sign: match[4] || '',
                    pad: match[5] || ' ',
                    min: match[6] || 0,
                    precision: match[8],
                    code: match[9] || '%',
                    negative: parseInt(arguments[convCount]) < 0 ? true : false,
                    argument: String(arguments[convCount])
                };
            }
            strings[strings.length] = string.substring(matchPosEnd);

            if (matches.length == 0) { return string; }
            if ((arguments.length - 1) < convCount) { return null; }

            var substitution = null;
            match = null;
            var i = null;

            for (i = 0; i < matches.length; i++) {

                if (matches[i].code == '%') { substitution = '%' } else if (matches[i].code == 'b') {
                    matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(2));
                    substitution = convert(matches[i], true);
                } else if (matches[i].code == 'c') {
                    matches[i].argument = String(String.fromCharCode(parseInt(Math.abs(parseInt(matches[i].argument)))));
                    substitution = convert(matches[i], true);
                } else if (matches[i].code == 'd') {
                    matches[i].argument = String(Math.abs(parseInt(matches[i].argument)));
                    substitution = convert(matches[i]);
                } else if (matches[i].code == 'f') {
                    matches[i].argument =
                    String(Math.abs(parseFloat(matches[i].argument)).toFixed(matches[i].precision ?
                                                                             matches[i].precision : 6));
                    substitution = convert(matches[i]);
                } else if (matches[i].code == 'o') {
                    matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(8));
                    substitution = convert(matches[i]);
                } else if (matches[i].code == 's') {
                    matches[i].argument = matches[i].argument.substring(0, matches[i].precision ?
                                                                           matches[i].precision :
                                                                           matches[i].argument.length)
                    substitution = convert(matches[i], true);
                } else if (matches[i].code == 'x') {
                    matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
                    substitution = convert(matches[i]);
                } else if (matches[i].code == 'X') {
                    matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
                    substitution = convert(matches[i]).toUpperCase();
                } else {
                    substitution = matches[i].match;
                }

                newString += strings[i];
                newString += substitution;

            }
            newString += strings[i];

            return newString;
        },

        eat: function(/* e */)
        {
        },

        createThrowable: function(msg,args)
        {
            var retval = new Error(msg);
            retval.stackdepth = 1;
            return retval;
        },

        makeLoggable: function(args)
        {
            if (args.length < 1)
                return null;

            if (args[0].message && typeof(args[0].stack) == "string")
            {
                // assume it's a built-in Error as we can't use instanceof down here.
                return args[0];
            }

            var msg = "UNRESOLVED";
            if (typeof(args[0]) != "string")
            {
                try { msg = "" + (args[0]) } catch (ex) { };
            }
            else
            {
                msg = lucidapi.sprintf.apply(null,args);
            }
            return { message: msg };
        },

        debug: function()
        {
            this.logimpl(3,this.makeLoggable(arguments));
        },

        info: function()
        {
            this.logimpl(2,this.makeLoggable(arguments));
        },

        warn: function()
        {
            this.logimpl(1,this.makeLoggable(arguments));
        },

        error: function()
        {
            this.logimpl(0,this.makeLoggable(arguments));
        },

        assert: function(truth)
        {
            if (truth)
            {
                return false
            }
            else
            {
                var newargs = [];
                for (var ii = 1; ii < arguments.length; ii++)
                    newargs.push( arguments[ii] );
                if (newargs.length < 1)
                    newargs.push("Assertion failed!");
                this.logimpl(1,this.makeLoggable(newargs));
                return true;
            }
        },

        logimpl: function(level,loggable)
        {
            // default no-op
        }
    }

    function lucid_extendClass(subClass, baseClass)
    {
        function inheritance()
        {
        }
        inheritance.prototype = baseClass.prototype;

        subClass.prototype = new inheritance();
        subClass.prototype.constructor = subClass;
        subClass.baseConstructor = baseClass;
        subClass.superClass = baseClass.prototype;
    };

    lucidapi.AbstractClass = function()
    {
    }

    lucidapi.AbstractClass.prototype = {
        initialize: function()
        {
        }
    }

    lucidapi.AbstractClass.extend = function(proto)
    {
        function subClass() {
            this.initialize.apply(this,arguments);
        }
        lucid_extendClass(subClass,this);
        if (typeof(proto) == "function")
            lucidapi.absorb(subClass.prototype,proto(subClass));
        else
            lucidapi.absorb(subClass.prototype,proto);

        subClass.extend = this.extend;
        return subClass;
    }

})();
